#include "pch.h"

#ifdef USE_GPU

//#include "FilterCS.h"
#include "CCGenGpu.h"

#include "CCGen.h"
#include "CJobDispatcher.h"
#include "half.hpp"

#include <cassert>
#include <D3DX11.h>
#include <d3dcompiler.h>

DXGI_FORMAT epf_to_dxgi_format(EPixelFormat format)
{
	switch (format)
	{
	case ePF_R8G8BA8:
		return DXGI_FORMAT_R8G8B8A8_UNORM;
	case ePF_R8G8BA8_UINT:
		return DXGI_FORMAT_R8G8B8A8_UINT;
	case ePF_R16G16B16A16F:
		return DXGI_FORMAT_R16G16B16A16_FLOAT;
	case ePF_VECTOR:
		return DXGI_FORMAT_R32G32B32A32_FLOAT;
	default:
		return DXGI_FORMAT_UNKNOWN;
	}
}

struct SCreateTapdirMessage
{
	TLUTData<Vec4> *initialData;
	unsigned int mipCount;
	STexture **outTexture;
};

struct SCreateFilterExtentsMessage
{
	TLUTData<SFilterExtentsLUT::FilterExtent> *initialData;
	unsigned int mipCount;
	STexture **outTexture;
};


CCGenGpu::CCGenGpu(CJobDispatcher *disp)
{
	gpuStatus = eGHS_invalid;
	gpuThread = nullptr;
	d3dDevice = nullptr;
	d3dDeviceContext = nullptr;
	computeShader = nullptr;
	filterConstantBuffer = nullptr;
	dispatcher = disp;
}


bool CCGenGpu::EnableGpu()
{
	if(gpuStatus == eGHS_invalid)
	{
		assert(gpuThread == nullptr);
	
		gpuThread = new tbb::tbb_thread(CCGenGpu::_GPUThreadEntry, this);

		while(gpuStatus == eGHS_invalid)
		{
			Sleep(1);
		}
	}

	if(gpuStatus == eGHS_valid)
	{
		return true;
	}
	else
	{
		return false;
	}
}


struct SFilterCBuffer
{
	float	dotProdThresh;
	float	specularPower;
	float	isPhongBRDF;
	float	faceIndex;		
	float	tapdirMip;		
	float	filterExtentMip;
	float	dstImageSize;	
	float	mainTapdirMip;	
	int		offsetX;		
	int		offsetY;		
	int		threadPerFilterExtent;		
	int		padding2;		
};

LRESULT CALLBACK wndProc(HWND hwnd, UINT m, WPARAM w, LPARAM l)
{
	return DefWindowProc(hwnd, m, w, l);
}

void CCGenGpu::_GPUThreadEntry(void *ptr)
{
	CCGenGpu *gpuHandler = (CCGenGpu *)ptr;
	CJobDispatcher *dispatcher = gpuHandler->dispatcher;

	assert(!gpuHandler->d3dDevice && !gpuHandler->d3dDeviceContext);

	D3D_FEATURE_LEVEL returnedfeatureLevel;
	D3D_FEATURE_LEVEL featureLevelToCreate = D3D_FEATURE_LEVEL_11_0;

	/*HRESULT res = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, 0, / *D3D11_CREATE_DEVICE_DEBUG* /0, &featureLevelToCreate, 1, D3D11_SDK_VERSION, &gpuHandler->d3dDevice, &returnedfeatureLevel, &gpuHandler->d3dDeviceContext);*/
	HRESULT res;

#ifdef ENABLE_GRAPHICS_DEBUGGING
	ID3D11Texture2D *backBuffer;
	ID3D11RenderTargetView *rtv;

	{
		WNDCLASS wc;

		ZeroMemory(&wc, sizeof(WNDCLASS));

		wc.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH);
		wc.hCursor = LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW));
		wc.lpfnWndProc = wndProc;
		wc.lpszClassName = "dummyWindow";

		RegisterClass(&wc);

		HWND new_window = CreateWindow("dummyWindow", "Test", 0, 1, 1, 500, 500, 0, 0, 0, 0);

		ShowWindow(new_window, SW_SHOW);

		DXGI_SWAP_CHAIN_DESC scd;
		ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));

		scd.BufferCount = 1;
		scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		scd.BufferDesc.Height = 500;
		scd.BufferDesc.RefreshRate.Numerator = 60;
		scd.BufferDesc.RefreshRate.Denominator = 1;
		scd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
		scd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
		scd.BufferDesc.Width = 500;

		scd.Flags = 0;
		scd.OutputWindow = new_window;
		scd.SampleDesc.Count = 1;
		scd.SampleDesc.Quality = 0;
		scd.Windowed = TRUE;
		scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

		res = D3D11CreateDeviceAndSwapChain(0, D3D_DRIVER_TYPE_HARDWARE, 0, 0, &featureLevelToCreate, 1, D3D11_SDK_VERSION,
			&scd, &gpuHandler->swapChain, &gpuHandler->d3dDevice, &returnedfeatureLevel, &gpuHandler->d3dDeviceContext);

		gpuHandler->swapChain->GetBuffer(0, __uuidof(backBuffer), reinterpret_cast<void**>(&backBuffer));

		gpuHandler->d3dDevice->CreateRenderTargetView(backBuffer, nullptr, &rtv);
		float clearCol[] = { 0.9, 0.5, 0.5, 0.5 };

		gpuHandler->d3dDeviceContext->ClearRenderTargetView(rtv, clearCol);
		HRESULT pr = gpuHandler->swapChain->Present(0, 0);
		assert(SUCCEEDED(pr));
	}
#else
	res = D3D11CreateDevice(0, D3D_DRIVER_TYPE_HARDWARE, 0, 0, &featureLevelToCreate, 1, D3D11_SDK_VERSION,
			&gpuHandler->d3dDevice, &returnedfeatureLevel, &gpuHandler->d3dDeviceContext);
#endif

	ID3D10Blob *shaderCode = nullptr;
	ID3D10Blob *errorBlob = nullptr;	

	while(!shaderCode)
	{
		//D3DCompile(g_computeShader, strlen(g_computeShader), nullptr, nullptr, nullptr, "main", "cs_5_0", 0, 0, &shaderCode, &errorBlob);
		//D3DCompileFromFile(L"../CCGen/FilterCS.hlsl", nullptr, nullptr, "main", "cs_5_0", 0, 0, &shaderCode, &errorBlob);
		res = D3DX11CompileFromFile("FilterCS.hlsl", nullptr, nullptr, "main", "cs_5_0", 0, 0, nullptr, &shaderCode, &errorBlob, nullptr);

		if (!shaderCode && errorBlob)
		{
			MessageBox(0,(char*)errorBlob->GetBufferPointer(), "Error", MB_OKCANCEL);
		}
	}	

	if(SUCCEEDED(res))
	{
		res = gpuHandler->d3dDevice->CreateComputeShader(shaderCode->GetBufferPointer(), shaderCode->GetBufferSize(), nullptr, &gpuHandler->computeShader);
	}

	if(SUCCEEDED(res))
	{
		D3D11_BUFFER_DESC bd;

		bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
		bd.ByteWidth = sizeof(SFilterCBuffer);
		bd.CPUAccessFlags = 0;
		bd.MiscFlags = 0;
		bd.StructureByteStride = 0;
		bd.Usage = D3D11_USAGE_DEFAULT;

		res = gpuHandler->d3dDevice->CreateBuffer(&bd, nullptr, &gpuHandler->filterConstantBuffer);
	}

	if(SUCCEEDED(res))
	{
		gpuHandler->gpuStatus = eGHS_valid;
	}
	else
	{
		gpuHandler->gpuStatus = eGHS_failed;

		SAFE_RELEASE(gpuHandler->d3dDevice);
		SAFE_RELEASE(gpuHandler->d3dDeviceContext);
		SAFE_RELEASE(gpuHandler->computeShader);
		SAFE_RELEASE(gpuHandler->filterConstantBuffer);
		SAFE_RELEASE(shaderCode);
		SAFE_RELEASE(errorBlob);
		return;
	}

	SFilterCBuffer filterCbuffer;

	gpuHandler->d3dDeviceContext->CSSetShader(gpuHandler->computeShader, nullptr, 0);

	while(1)
	{
		SGpuThreadMessage msg;
		if(gpuHandler->message_queue.try_pop(msg))
		{
			switch(msg.code)
			{
			case eGMC_filter_cubemap:
				{
					SGpuFilterCubemapJob *filterJob = (SGpuFilterCubemapJob*)msg.data;
					
					STexture *srcTexture = nullptr;
					STexture *dstTextures = nullptr;
					STexture *stagingTextures = nullptr;

					gpuHandler->GetFilterTextures(filterJob->filterState, filterJob->filterStateData, &dstTextures, &srcTexture, &stagingTextures);

					int pixelByteWidth = epf_pixel_size(filterJob->filterStateData->desc.srcPixelFormat);

					//Update source cubemap
					for(int i_face = 0; i_face < 6; i_face++)
					{
						SCubeFace &curFace = filterJob->inputFaces[i_face];

						ID3D11Texture2D *srcTextureHandle = srcTexture->texture;
						gpuHandler->d3dDeviceContext->UpdateSubresource(srcTextureHandle, i_face, nullptr, curFace.dataPtr, curFace.width * pixelByteWidth, 0);
					}
					
					for (int i_job = 0; i_job < filterJob->jobCount; i_job++)
					{
						SGpuFilterLevelJob *curJob = &filterJob->filterJobs[i_job];	

						unsigned int dstMipCount = (int)CGen::log2((float)curJob->job.filterStateData->desc.dstSize) + 1;
						unsigned int dstCubemapMipIndex = (int)CGen::log2((float)curJob->job.filterStateData->desc.dstSize) - (int)CGen::log2((float)curJob->job.outputCubeFace.width); //TODO divide logs ?
						unsigned int dstCubemapSurfaceIndex = curJob->job.outputFaceIndex * dstMipCount + dstCubemapMipIndex;
						unsigned int feMipIndex = curJob->filterExtentsLut->mipCount - (int)(CGen::log2((float)curJob->job.outputCubeFace.width) + 1);
						unsigned int dstTapDirMipIndex = curJob->tapdirLut->mipCount - (int)(CGen::log2((float)curJob->job.outputCubeFace.width) + 1);
						unsigned int srcTapDirMipIndex = curJob->tapdirLut->mipCount - (int)(CGen::log2((float)curJob->job.inputCubeFaces->width) + 1);

						filterCbuffer.dotProdThresh = curJob->job.filterStateData->dotpThreshold[dstCubemapMipIndex];
						filterCbuffer.dstImageSize = (float)curJob->job.outputCubeFace.width;
						filterCbuffer.faceIndex = (float)curJob->job.outputFaceIndex;
						filterCbuffer.filterExtentMip = (float)feMipIndex;
						if(curJob->job.filterStateData->desc.lightingModel == CP_LIGHTINGMODEL_BLINN_BRDF 
							|| curJob->job.filterStateData->desc.lightingModel == CP_LIGHTINGMODEL_PHONG_BRDF)
						{
							filterCbuffer.isPhongBRDF = 1.0f;
						}
						else
						{
							filterCbuffer.isPhongBRDF = 0.0f;
						}
						filterCbuffer.mainTapdirMip = (float)dstTapDirMipIndex;
						filterCbuffer.offsetX = 0;
						filterCbuffer.offsetY = 0;
						filterCbuffer.specularPower = curJob->job.filterStateData->specularPowers[dstCubemapMipIndex];
						filterCbuffer.tapdirMip = (float)srcTapDirMipIndex;

						float filterSize = (float)curJob->job.filterStateData->filterSizes[dstCubemapMipIndex];
						unsigned int idealThreadCount = 0;
						
						if (filterSize < 32)
						{
							idealThreadCount = 8;
						}
						else if (filterSize < 128)
						{
							idealThreadCount = 8;
						}
						else
						{
							idealThreadCount = 16;
						}

						filterCbuffer.threadPerFilterExtent = idealThreadCount;

						gpuHandler->d3dDeviceContext->UpdateSubresource(gpuHandler->filterConstantBuffer, 0, nullptr, &filterCbuffer, 0, 0);
						gpuHandler->d3dDeviceContext->CSSetShaderResources(0, 1, &curJob->tapdirLut->gpuDataPtrs->textureSRV);
						gpuHandler->d3dDeviceContext->CSSetShaderResources(1, 1, &curJob->filterExtentsLut->gpuDataPtrs->textureSRV);
						gpuHandler->d3dDeviceContext->CSSetShaderResources(2, 1, &srcTexture->textureSRV);
						gpuHandler->d3dDeviceContext->CSSetUnorderedAccessViews(0, 1, &dstTextures[dstCubemapSurfaceIndex].textureUAV, nullptr);
						gpuHandler->d3dDeviceContext->CSSetConstantBuffers(0, 1, &gpuHandler->filterConstantBuffer);

						gpuHandler->d3dDeviceContext->Dispatch(
							(idealThreadCount * curJob->job.outputCubeFace.width) / 16,
							(idealThreadCount * curJob->job.outputCubeFace.width) / 16,
							1);

						gpuHandler->d3dDeviceContext->Flush();

						printf("Dispatch finished for %d\n", curJob->job.outputCubeFace.width);

						filterJob->parentJob->internalCounter--;
					}

#ifdef ENABLE_GRAPHICS_DEBUGGING
					float clearColor[] = { 0.0, 0.5, 0.5, 0.5 };

					gpuHandler->d3dDeviceContext->ClearRenderTargetView(rtv, clearColor);
					HRESULT pr = gpuHandler->swapChain->Present(0, 0);
					assert(SUCCEEDED(pr));
#endif

					for(int i_job = 0; i_job < filterJob->jobCount; i_job++)
					{
						SGpuFilterLevelJob *curJob = &filterJob->filterJobs[i_job];

						unsigned int dstMipCount = (int)CGen::log2((float)curJob->job.filterStateData->desc.dstSize) + 1;
						unsigned int dstCubemapMipIndex = (int)CGen::log2((float)curJob->job.filterStateData->desc.dstSize) - (int)CGen::log2((float)curJob->job.outputCubeFace.width);
						unsigned int dstCubemapSurfaceIndex = curJob->job.outputFaceIndex * dstMipCount + dstCubemapMipIndex;
						
						ID3D11Texture2D *stagingTexture = stagingTextures[dstCubemapMipIndex].texture;
						ID3D11Texture2D *textureToMap = dstTextures[dstCubemapSurfaceIndex].texture;

						gpuHandler->d3dDeviceContext->CopyResource(stagingTexture, textureToMap);

						D3D11_MAPPED_SUBRESOURCE msr;

						HRESULT res = gpuHandler->d3dDeviceContext->Map(stagingTexture, 0, D3D11_MAP_READ, 0, &msr);

						if(SUCCEEDED(res))
						{
							if(msr.RowPitch == epf_pixel_size(curJob->job.filterStateData->desc.dstPixelFormat) * curJob->job.outputCubeFace.width)
							{
								memcpy_s(curJob->job.outputCubeFace.dataPtr, curJob->job.outputCubeFace.dataSize, msr.pData, msr.RowPitch * curJob->job.outputCubeFace.height);
							}
							else
							{
								unsigned char* targetPtr = (unsigned char*)curJob->job.outputCubeFace.dataPtr;
								unsigned char* srcPtr = (unsigned char*)msr.pData;

								unsigned int targetPitch = epf_pixel_size(curJob->job.filterStateData->desc.dstPixelFormat) * curJob->job.outputCubeFace.width;
								unsigned int srcPitch = msr.RowPitch;

								for(unsigned int i_h = 0; i_h < curJob->job.outputCubeFace.height; i_h++)
								{
									memcpy(targetPtr, srcPtr, targetPitch);

									targetPtr += targetPitch;
									srcPtr += srcPitch;
								}
							}

							gpuHandler->d3dDeviceContext->Unmap(stagingTexture, 0);
						}
					}

					assert(filterJob->parentJob->internalCounter == 0);

					dispatcher->RemoveActiveJobAndNotify(filterJob->parentJob);

					InterlockedIncrement(&dispatcher->processedJobCount);

					SAFE_DELETE(filterJob);
				}
				break;

			case eGMC_create_tapdir_lut:
				{
					SCreateTapdirMessage *msg_data = (SCreateTapdirMessage *)msg.data;
					gpuHandler->CreateTapdirLUT_imp(msg_data->initialData, msg_data->mipCount, msg_data->outTexture);
					SAFE_DELETE(msg_data);
				}
				break;

			case eGMC_create_filter_extents_lut:
				{
					SCreateFilterExtentsMessage *msg_data = (SCreateFilterExtentsMessage *)msg.data;
					gpuHandler->CreateFilterExtentLUT_imp(msg_data->initialData, msg_data->mipCount, msg_data->outTexture);
					SAFE_DELETE(msg_data);
				}
				break;

			case eGMC_destroy_thread:
				return;

			default:
				assert(false);
			}
		}
		else
		{
#ifdef ENABLE_GRAPHICS_DEBUGGING
			MSG msg;
			while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
#endif
			Sleep(1000);
		}
	}
}

void CCGenGpu::SendMessage( const SGpuThreadMessage& msg )
{
	message_queue.push(msg);
}

void CCGenGpu::CreateTapdirLUT_imp( TLUTData<Vec4> *initialData, unsigned int mipCount, STexture **outTexture )
{
	ID3D11Texture2D *texture = nullptr;
	ID3D11ShaderResourceView *srv = nullptr;

	D3D11_TEXTURE2D_DESC td;
	
	td.ArraySize = 6;
	td.BindFlags = D3D11_BIND_SHADER_RESOURCE;
	td.CPUAccessFlags = 0;
	td.Format = DXGI_FORMAT_R16G16B16A16_FLOAT;
	td.Height = initialData->height;
	td.MipLevels = mipCount;
	td.MiscFlags = 0;
	td.SampleDesc.Count = 1;
	td.SampleDesc.Quality = 0;
	td.Usage = D3D11_USAGE_IMMUTABLE;
	td.Width = initialData->width;

	D3D11_SUBRESOURCE_DATA *srd = new D3D11_SUBRESOURCE_DATA[6 * mipCount];

	for(unsigned int i_face = 0, i_surface = 0; i_face < 6; i_face++)
	{
		for(unsigned int i_mip = 0; i_mip < mipCount; i_mip++)
		{
			unsigned int mipSize = initialData[i_surface].width;
			unsigned int pixelCount = mipSize * mipSize;

			unsigned __int16* convertedData = new unsigned __int16[pixelCount * 4];
			unsigned __int16* convertedDataCursor = (unsigned __int16*)convertedData;
			Vec4 *floatDataCursor = initialData[i_surface].data;

			for(unsigned int i_pixel = 0; i_pixel < pixelCount; i_pixel++)
			{
// 				convertedDataCursor[0] = ((floatDataCursor->x + 1.0) * 0.5f) * 255.0f;
// 				convertedDataCursor[1] = ((floatDataCursor->y + 1.0) * 0.5f) * 255.0f;
// 				convertedDataCursor[2] = ((floatDataCursor->z + 1.0) * 0.5f) * 255.0f;
// 				convertedDataCursor[3] = floatDataCursor->w;

				convertedDataCursor[0] = half_float::detail::float2half<std::round_to_nearest>(floatDataCursor->x);
				convertedDataCursor[1] = half_float::detail::float2half<std::round_to_nearest>(floatDataCursor->y);
				convertedDataCursor[2] = half_float::detail::float2half<std::round_to_nearest>(floatDataCursor->z);
				convertedDataCursor[3] = half_float::detail::float2half<std::round_to_nearest>(floatDataCursor->w);

				convertedDataCursor += 4;
				floatDataCursor++;
			}

			srd[i_surface].pSysMem = convertedData;
			srd[i_surface].SysMemPitch = initialData[i_surface].width * 8;

			i_surface++;
		}
	}

	HRESULT res = d3dDevice->CreateTexture2D(&td, srd, &texture);

	int surfaceCount = mipCount * 6;
	for(int i_surface = 0; i_surface < surfaceCount; i_surface++)
	{
		SAFE_DELETE_ARRAY(srd[i_surface].pSysMem);
	}
	SAFE_DELETE_ARRAY(srd);

	if(SUCCEEDED(res))
	{
		res = d3dDevice->CreateShaderResourceView(texture, nullptr, &srv);
	}

	if(SUCCEEDED(res))
	{
		(*outTexture)->texture = texture;
		(*outTexture)->textureSRV = srv;
		(*outTexture)->status = eGHS_valid;
	}
	else
	{
		SAFE_RELEASE(texture);
		SAFE_RELEASE(srv);
		(*outTexture)->status = eGHS_failed;
	}
}

void CCGenGpu::CreateFilterExtentLUT_imp( TLUTData<SFilterExtentsLUT::FilterExtent> *initialData, unsigned int mipCount, STexture **outTexture )
{
	ID3D11Texture2D *texture = nullptr;
	ID3D11ShaderResourceView *srv = nullptr;

	D3D11_TEXTURE2D_DESC td;

	td.ArraySize = 6;
	td.BindFlags = D3D11_BIND_SHADER_RESOURCE;
	td.CPUAccessFlags = 0;
	td.Format = DXGI_FORMAT_R16G16B16A16_UINT;
	td.Height = initialData->height;
	td.MipLevels = mipCount;
	td.MiscFlags = 0;
	td.SampleDesc.Count = 1;
	td.SampleDesc.Quality = 0;
	td.Usage = D3D11_USAGE_IMMUTABLE;
	td.Width = initialData->width * 6;

	D3D11_SUBRESOURCE_DATA *srd = new D3D11_SUBRESOURCE_DATA[6 * mipCount];

	for(unsigned int i_face = 0, i_surface = 0; i_face < 6; i_face++)
	{
		for(unsigned int i_mip = 0; i_mip < mipCount; i_mip++)
		{
			__int16 *convertedInitialData = new __int16[initialData[i_surface].width * 4 * 6 * initialData[i_surface].height];
			__int16 *convertedInitialDataCursor = convertedInitialData;

			for(unsigned int i_h = 0, i_pixel = 0; i_h < initialData[i_surface].height; i_h++)
			{
				for(unsigned int i_w = 0; i_w < initialData[i_surface].width; i_w++)
				{
					convertedInitialDataCursor[0] = initialData[i_surface].data[i_pixel].minTexelX[0];
					convertedInitialDataCursor[1] = initialData[i_surface].data[i_pixel].minTexelY[0];
					convertedInitialDataCursor[2] = initialData[i_surface].data[i_pixel].maxTexelX[0];
					convertedInitialDataCursor[3] = initialData[i_surface].data[i_pixel].maxTexelY[0];

					convertedInitialDataCursor += 4;

					convertedInitialDataCursor[0] = initialData[i_surface].data[i_pixel].minTexelX[1];
					convertedInitialDataCursor[1] = initialData[i_surface].data[i_pixel].minTexelY[1];
					convertedInitialDataCursor[2] = initialData[i_surface].data[i_pixel].maxTexelX[1];
					convertedInitialDataCursor[3] = initialData[i_surface].data[i_pixel].maxTexelY[1];

					convertedInitialDataCursor += 4;

					convertedInitialDataCursor[0] = initialData[i_surface].data[i_pixel].minTexelX[2];
					convertedInitialDataCursor[1] = initialData[i_surface].data[i_pixel].minTexelY[2];
					convertedInitialDataCursor[2] = initialData[i_surface].data[i_pixel].maxTexelX[2];
					convertedInitialDataCursor[3] = initialData[i_surface].data[i_pixel].maxTexelY[2];

					convertedInitialDataCursor += 4;

					convertedInitialDataCursor[0] = initialData[i_surface].data[i_pixel].minTexelX[3];
					convertedInitialDataCursor[1] = initialData[i_surface].data[i_pixel].minTexelY[3];
					convertedInitialDataCursor[2] = initialData[i_surface].data[i_pixel].maxTexelX[3];
					convertedInitialDataCursor[3] = initialData[i_surface].data[i_pixel].maxTexelY[3];

					convertedInitialDataCursor += 4;

					convertedInitialDataCursor[0] = initialData[i_surface].data[i_pixel].minTexelX[4];
					convertedInitialDataCursor[1] = initialData[i_surface].data[i_pixel].minTexelY[4];
					convertedInitialDataCursor[2] = initialData[i_surface].data[i_pixel].maxTexelX[4];
					convertedInitialDataCursor[3] = initialData[i_surface].data[i_pixel].maxTexelY[4];

					convertedInitialDataCursor += 4;

					convertedInitialDataCursor[0] = initialData[i_surface].data[i_pixel].minTexelX[5];
					convertedInitialDataCursor[1] = initialData[i_surface].data[i_pixel].minTexelY[5];
					convertedInitialDataCursor[2] = initialData[i_surface].data[i_pixel].maxTexelX[5];
					convertedInitialDataCursor[3] = initialData[i_surface].data[i_pixel].maxTexelY[5];

					convertedInitialDataCursor += 4;
					i_pixel++;
				}
			}

			srd[i_surface].pSysMem = convertedInitialData;
			srd[i_surface].SysMemPitch = initialData[i_surface].width * sizeof(__int16) * 4 * 6;
			srd[i_surface].SysMemSlicePitch = 0;

			i_surface++;
		}
	}

	HRESULT res = d3dDevice->CreateTexture2D(&td, srd, &texture);

	int surfaceCount = mipCount * 6;
	for(int i_surface = 0; i_surface < surfaceCount; i_surface++)
	{
		SAFE_DELETE_ARRAY(srd[i_surface].pSysMem);
	}
	SAFE_DELETE_ARRAY(srd);

	if(SUCCEEDED(res))
	{
		res = d3dDevice->CreateShaderResourceView(texture, nullptr, &srv);
	}

	if(SUCCEEDED(res))
	{
		*outTexture = new STexture;
		(*outTexture)->texture = texture;
		(*outTexture)->textureSRV = srv;
		(*outTexture)->status = eGHS_valid;
	}
	else
	{
		SAFE_RELEASE(texture);
		SAFE_RELEASE(srv);
		(*outTexture)->status = eGHS_failed;
	}
}

void CCGenGpu::CreateTapdirLUT( TLUTData<Vec4> *initialData, unsigned int mipCount, STexture **outTexture )
{
	SGpuThreadMessage msg;

	SCreateTapdirMessage *msg_data = new SCreateTapdirMessage;

	msg_data->initialData = initialData;
	msg_data->mipCount = mipCount;
	msg_data->outTexture = outTexture;

	msg.code = eGMC_create_tapdir_lut;
	msg.data = msg_data;

	SendMessage(msg);

	while((*outTexture)->status == eGHS_invalid)
	{
		Sleep(1);
	}
}

void CCGenGpu::CreateFilterExtentLUT( TLUTData<SFilterExtentsLUT::FilterExtent> *initialData, unsigned int mipCount, STexture **outTexture )
{
	SGpuThreadMessage msg;

	SCreateFilterExtentsMessage *msg_data = new SCreateFilterExtentsMessage;

	msg_data->initialData = initialData;
	msg_data->mipCount = mipCount;
	msg_data->outTexture = outTexture;

	msg.code = eGMC_create_filter_extents_lut;
	msg.data = msg_data;

	SendMessage(msg);

	while((*outTexture)->status == eGHS_invalid)
	{
		Sleep(1);
	}
}

void CCGenGpu::GetFilterTextures(const SFilterState *filterState, const SFilterStateData *filterStateData, STexture **outDstTexture, STexture **outSrcTexture, STexture **outStagingTexture )
{
	assert(filterState->GetStateID() != -1);

	SFilterTextures *filterTexturePair = filterTextures[filterState->GetStateID()];

	if(filterTexturePair)
	{
		*outDstTexture= filterTexturePair->dstTextures;
		*outSrcTexture= &filterTexturePair->srcTexture;
		*outStagingTexture = filterTexturePair->stagingTextures;
	}
	else
	{
		SFilterTextures *filterTextureGroup = new SFilterTextures;

#ifdef USE_DX11
		HRESULT res;

		ID3D11Texture2D	*srcCubeTexture = nullptr;
		ID3D11ShaderResourceView *srcCubeTexture_srv = nullptr;

		ID3D11Texture2D **dstTextures = nullptr;
		ID3D11UnorderedAccessView **dstTexture_uavs = nullptr;

		ID3D11Texture2D **stagingTextures = nullptr;

		int dstMipCount = (int)(CGen::log2((float)filterStateData->desc.dstSize) + 1);

		{
			//Src texture
			D3D11_TEXTURE2D_DESC td;

			td.ArraySize = 6;
			td.BindFlags = D3D11_BIND_SHADER_RESOURCE;
			td.CPUAccessFlags = 0;
			td.Format = epf_to_dxgi_format(filterStateData->desc.srcPixelFormat);
			td.Height = filterStateData->desc.srcSize;
			td.Width = filterStateData->desc.srcSize;
			td.MipLevels = 1;
			td.MiscFlags = 0;
			td.SampleDesc.Count = 1;
			td.SampleDesc.Quality = 0;
			td.Usage = D3D11_USAGE_DEFAULT;

			res = d3dDevice->CreateTexture2D(&td, nullptr, &srcCubeTexture);

			if(SUCCEEDED(res))
			{
				res = d3dDevice->CreateShaderResourceView(srcCubeTexture, nullptr, &srcCubeTexture_srv);
			}

			//Dst texture

			if(SUCCEEDED(res))
			{
				dstTextures = new ID3D11Texture2D*[dstMipCount * 6];
				dstTexture_uavs = new ID3D11UnorderedAccessView*[dstMipCount * 6];

				for(int i_face = 0, i_surface = 0; i_face < 6; i_face++)
				{
					unsigned int mipSize = filterStateData->desc.dstSize;

					for(int i_mip = 0; i_mip < dstMipCount; i_mip++)
					{
						ID3D11Texture2D **curTexture = &dstTextures[i_surface];
						ID3D11UnorderedAccessView **curTextureUav = &dstTexture_uavs[i_surface];

						td.ArraySize = 1;
						td.BindFlags = D3D11_BIND_UNORDERED_ACCESS;
						td.Format = epf_to_dxgi_format(filterStateData->desc.dstPixelFormat);
						td.Height = mipSize;
						td.Width = mipSize;
						td.Usage = D3D11_USAGE_DEFAULT;
						td.CPUAccessFlags = 0;

						res = d3dDevice->CreateTexture2D(&td, nullptr, curTexture);

						if(SUCCEEDED(res))
						{
							res = d3dDevice->CreateUnorderedAccessView(*curTexture, nullptr, curTextureUav);
						}

						mipSize = mipSize >> 1;

						i_surface++;
					}
				}
			}

			//Staging texture
			if(SUCCEEDED(res))
			{				
				stagingTextures = new ID3D11Texture2D *[dstMipCount];

				unsigned int mipSize = filterStateData->desc.dstSize;

				for(int i_mip = 0; i_mip < dstMipCount; i_mip++)
				{
					ID3D11Texture2D **curTexture = &stagingTextures[i_mip];
					td.Usage = D3D11_USAGE_STAGING;
					td.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
					td.BindFlags = 0;
					td.Width = mipSize;
					td.Height = mipSize;

					res = d3dDevice->CreateTexture2D(&td, nullptr, curTexture);

					mipSize = mipSize >> 1;
				}			
			}

			if(FAILED(res))
			{
// 				SAFE_RELEASE(srcCubeTexture);
// 				SAFE_RELEASE(srcCubeTexture_srv);
// 				SAFE_RELEASE(dstTexture);
// 				SAFE_RELEASE(dstTexture_uav);
// 				SAFE_RELEASE(stagingTexture);
// 
// 				filterTextureGroup->srcTexture.texture = nullptr;
// 				filterTextureGroup->srcTexture.textureSRV = nullptr;
// 				filterTextureGroup->srcTexture.textureUAV = nullptr;
// 				filterTextureGroup->srcTexture.status = eGHS_failed;
// 
// 				filterTextureGroup->stagingTexture.texture = nullptr;
// 				filterTextureGroup->stagingTexture.textureSRV = nullptr;
// 				filterTextureGroup->stagingTexture.textureUAV = nullptr;
// 				filterTextureGroup->stagingTexture.status = eGHS_failed;
// 
// 				filterTextureGroup->dstTexture.texture = nullptr;
// 				filterTextureGroup->dstTexture.textureSRV = nullptr;
// 				filterTextureGroup->dstTexture.textureUAV = nullptr;
// 				filterTextureGroup->dstTexture.status = eGHS_failed;
			}
			else
			{
				filterTextureGroup->srcTexture.texture = srcCubeTexture;
				filterTextureGroup->srcTexture.textureSRV = srcCubeTexture_srv;
				filterTextureGroup->srcTexture.textureUAV = nullptr;
				filterTextureGroup->srcTexture.status = eGHS_valid;

				filterTextureGroup->stagingTextures = new STexture[dstMipCount];
				for(int i_mip = 0; i_mip < dstMipCount; i_mip++)
				{
					filterTextureGroup->stagingTextures[i_mip].texture = stagingTextures[i_mip];
					filterTextureGroup->stagingTextures[i_mip].textureSRV = nullptr;
					filterTextureGroup->stagingTextures[i_mip].textureUAV = nullptr;
					filterTextureGroup->stagingTextures[i_mip].status = eGHS_valid;					
				}

				filterTextureGroup->dstTextures = new STexture[dstMipCount * 6];
				for(int i_surface = 0; i_surface < dstMipCount * 6; i_surface++)
				{
					filterTextureGroup->dstTextures[i_surface].texture = dstTextures[i_surface];
					filterTextureGroup->dstTextures[i_surface].textureSRV = nullptr;
					filterTextureGroup->dstTextures[i_surface].textureUAV = dstTexture_uavs[i_surface];
					filterTextureGroup->dstTextures[i_surface].status = eGHS_valid;					
				}
			}

			*outSrcTexture = &filterTextureGroup->srcTexture;
			*outDstTexture = filterTextureGroup->dstTextures;
			*outStagingTexture = filterTextureGroup->stagingTextures;

			filterTextures[filterState->GetStateID()] = filterTextureGroup;
		}
#endif
	}
}

void CCGenGpu::WaitForAll()
{
	while(!message_queue.empty())
	{
		Sleep(10);
	}
}

CCGenGpu::~CCGenGpu()
{
	if(gpuThread)
	{
		SGpuThreadMessage msg;

		msg.code = eGMC_destroy_thread;
		msg.data = nullptr;

		SendMessage(msg);

		gpuThread->join();

		SAFE_DELETE(gpuThread);
	}

	auto it = filterTextures.begin();
	while(it != filterTextures.end())
	{
		SAFE_DELETE(it->second);
		it++;
	}
}

#endif